package com.google.android.daca.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlSerializer;

import android.util.Log;
import android.util.Xml;

import com.google.android.daca.types.Initiator;
import com.google.android.daca.types.Option;
import com.google.android.daca.types.Participant;
import com.google.android.daca.types.Poll;
import com.google.android.daca.types.States;
/**
 * Class that is used for de/serializing requests and responses
 * @author pavel
 *
 */
public class XMLUtil {
	/**
	 * Creates a Poll object from DOM tree.
	 * @param doc {@link Document} which contains a poll response from the server
	 * @return {@link Poll} object representing this DOM tree
	 * @throws IllegalArgumentException if the Document does not contain exactly one {http://doodle.com/xsd1}poll element
	 */
	public static Poll getPollFromDocument(Document doc) {
		Poll result = new Poll();
		NodeList rootNode = doc.getElementsByTagName("poll");
		if (rootNode.getLength() != 1) {
			throw new IllegalArgumentException("Incorrect document. It contains [" + rootNode.getLength() +"] polls");
		}
		NodeList properties = rootNode.item(0).getChildNodes();
		for (int i = 0; i < properties.getLength(); ++i) {
			Node property = properties.item(i);
			String propertyName = property.getNodeName();
			if ("options".equals(propertyName)) {
				result.setOptions(parseOptions((Element) property));
			} else if ("title".equals(propertyName)) {
				result.setTitle(property.getFirstChild().getNodeValue());
			} else if ("state".equals(propertyName)) {
				result.setState(States.valueOf(property.getFirstChild().getNodeValue()));
			} else if ("levels".equals(propertyName)) {
				result.setLevels(Integer.valueOf(property.getFirstChild().getNodeValue()));
			} else if ("hidden".equals(propertyName)) {
				result.setHidden(Boolean.valueOf(property.getFirstChild().getNodeValue()));
			} else if ("description".equals(propertyName) && property.hasChildNodes()) {
				result.setDescription(property.getFirstChild().getNodeValue());
			} else if ("initiator".equals(propertyName)) {
				result.setInitiator(parseInitiator((Element) property));
			} else if ("participants".equals(propertyName)) {
				result.setParticipants(parseParticipants((Element) property));
			}
		}
		return result;
	}

	private static List<Participant> parseParticipants(Element participantsTypeNode) {
		final String TAG = "XMLUtil#parseParticipants";
		List<Participant> result = new ArrayList<Participant>();
		NodeList participants = participantsTypeNode.getElementsByTagName("participant");
		for (int i = 0; i < participants.getLength(); ++i) {
			Participant participant = new Participant();
			Node currentParticipantNode = participants.item(i);
			NodeList currentProperties = currentParticipantNode.getChildNodes();
			for (int j = 0; j < currentProperties.getLength(); ++j) {
				Node currentProperty = currentProperties.item(j);
				String propertyName = currentProperty.getNodeName();
				if ("id".equals(propertyName) && currentProperty.hasChildNodes()) {
					participant.setId(Long.valueOf(currentProperty.getFirstChild().getNodeValue()));
				} else if ("name".equals(propertyName)) {
					participant.setName(currentProperty.getFirstChild().getNodeValue());
				} else if ("participantKey".equals(propertyName) && currentProperty.hasChildNodes()) {
					participant.setParticipantKey(currentProperty.getFirstChild().getNodeValue());
				} else if ("userId".equals(propertyName) && currentProperty.hasChildNodes()) {
					participant.setUserId(currentProperty.getFirstChild().getNodeValue());
				} else if ("preferences".equals(propertyName)) {
					NodeList optionNodes = currentProperty.getChildNodes();
					List<Integer> preferences = new ArrayList<Integer>();
					for (int k = 0; k < optionNodes.getLength(); ++k) {
						preferences.add(Integer.valueOf(optionNodes.item(k).getFirstChild().getNodeValue()));
					}
					participant.setPreferences(preferences);
				}
			}
			result.add(participant);
		}
		return result;
	}

	private static Initiator parseInitiator(Element property) {
		Initiator result = new Initiator();
		NodeList children = property.getElementsByTagName("name");
		if (children.getLength() == 1) {
			result.setName(children.item(0).getFirstChild().getNodeValue());
		}
		return result;
	}

	public static String serializeParticipant(Participant participant) {
		StringBuffer result = new StringBuffer();
		result.append("<tns:participant xmlns:tns=\"http://doodle.com/xsd1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://doodle.com/xsd1 participant.xsd \">\n");
		result.append("<tns:id>");
		result.append(participant.getId());
		result.append("</tns:id>\n");
		result.append("<tns:participantKey>");
		result.append(participant.getParticipantKey());
		result.append("</tns:participantKey>\n");
		result.append("<tns:name>");
		result.append(participant.getName());
		result.append("</tns:name>\n");
		result.append("<tns:userId>");
		result.append(participant.getUserId());
		result.append("</tns:userId>\n");
		result.append("<tns:preferences>\n");

		List<Integer> prefs = participant.getPreferences();
		for (Integer integer : prefs) {
			result.append("<tns:option>");
			result.append(integer);
			result.append("</tns:option>\n");
		}

		result.append("</tns:preferences>\n");
		result.append("</tns:participant>");

		return result.toString();
	}

	/**
	 * This method creates XML representation of {@link Poll} object according to poll.xsd
	 * For now it's just a mock-up
	 * @param poll
	 * @return serialized poll
	 */
	public static String serializePoll(Poll poll) throws Exception{
		XmlSerializer serializer = Xml.newSerializer();
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		try {
			serializer.setOutput(output, "UTF-8");
			serializer.startDocument("UTF-8", null);
			serializer.setPrefix("", "http://doodle.com/xsd1");
			serializer.startTag("http://doodle.com/xsd1", "poll");

			serializer.startTag(null, "type");
			serializer.text(poll.getType().toString());
			serializer.endTag(null, "type");

			serializer.startTag(null, "hidden");
			serializer.text(poll.isHidden().toString());
			serializer.endTag(null, "hidden");

			serializer.startTag(null, "levels");
			serializer.text(poll.getLevels().toString());
			serializer.endTag(null, "levels");

			serializer.startTag(null, "state");
			serializer.text(poll.getState().toString());
			serializer.endTag(null, "state");

			serializer.startTag(null, "title");
			serializer.text(poll.getTitle());
			serializer.endTag(null, "title");

			serializer.startTag(null, "description");
			serializer.text(poll.getDescription());
			serializer.endTag(null, "description");

			serializer.startTag(null, "initiator");

			serializer.startTag(null, "name");
			serializer.text(poll.getInitiator().getName());
			serializer.endTag(null, "name");

			serializer.endTag(null, "initiator");

			serializer.startTag(null, "options");

			for (Option opt : poll.getOptions()) {
				serializer.startTag(null, "option");
				serializer.text(opt.getValue());
				serializer.endTag(null, "option");
			}

			serializer.endTag(null, "options");

			serializer.endTag("http://doodle.com/xsd1", "poll");
			serializer.endDocument();

			return output.toString("UTF-8");
		} catch (IOException ioEx) {
			throw new Exception("Error while serializing poll", ioEx);
		}
	}
	/**
	 * Parses {http://doodle.com/xsd1}options element and returns a list of {@link Option}
	 * @param optionsNode The root options element
	 * @return
	 */
	private static List<Option> parseOptions(Element optionsNode) {
		final String TAG = "XMLUtil#parseOptions";
		List<Option> result = new ArrayList<Option>();
		NodeList options = optionsNode.getElementsByTagName("option");
		for (int i = 0; i < options.getLength(); ++i) {
			Option option = new Option();
			Node currentOptionNode = options.item(i);
			if (currentOptionNode.hasChildNodes()) {
				option.setValue(currentOptionNode.getFirstChild().getNodeValue());
			}
			NamedNodeMap attrs = currentOptionNode.getAttributes();
			try {
				for (int j = 0; j < attrs.getLength(); ++j) {
					Attr attr = (Attr) attrs.item(j);
					if ("date".equals(attr.getNodeName())) {
						GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance();
						SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
						calendar.setTime(dateFormat.parse(attr.getNodeValue()));
						option.setDate(calendar);
					} else if ("dateTime".equals(attr.getNodeName())) {
						GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance();
						SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
						calendar.setTime(dateFormat.parse(attr.getNodeValue()));
						option.setDateTime(calendar);
					} else if ("startDateTime".equals(attr.getNodeName())) {
						GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance();
						SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
						calendar.setTime(dateFormat.parse(attr.getNodeValue()));
						option.setStartDateTime(calendar);
					} else if ("endDateTime".equals(attr.getNodeName())) {
						GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance();
						SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
						calendar.setTime(dateFormat.parse(attr.getNodeValue()));
						option.setEndDateTime(calendar);
					} else if ("final".equals(attr.getNodeName())) {
						option.setFinal(Boolean.parseBoolean(attr.getNodeValue()));
					} else {
						throw new IllegalArgumentException("Unexpected attribute found " +
								attr.getNodeName() + "=" + attr.getNodeValue());
					}
				}
			} catch (ParseException pEx) {
				Log.w(TAG, "Error while parsing options. Some of the date related fields will be blank" + Log.getStackTraceString(pEx));
			}
			result.add(option);
		}
		return result;
	}


}
